import java.util.ArrayList;
import java.util.List;
import java.util.Set;

import org.eclipse.draw2d.AbstractConnectionAnchor;
import org.eclipse.draw2d.ConnectionRouter;
import org.eclipse.draw2d.Figure;
import org.eclipse.draw2d.Graphics;
import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.LightweightSystem;
import org.eclipse.draw2d.MouseEvent;
import org.eclipse.draw2d.MouseListener;
import org.eclipse.draw2d.MouseMotionListener;
import org.eclipse.draw2d.PolygonDecoration;
import org.eclipse.draw2d.PolylineConnection;
import org.eclipse.draw2d.XYLayout;
import org.eclipse.draw2d.geometry.Dimension;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.draw2d.geometry.PrecisionPoint;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.widgets.Dialog;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;


/**
 * This dialog is used to show task graph after user has input all the tasks
 * and set proper preorders between them.
 * 
 * @author zhongnanhai
 *
 */
public class TaskGraphDlg extends Dialog{
	/**All of the tasks this dialog will show with graphs*/
	private List<Task> tasks = new ArrayList<Task>();
	/**
	 * A constructor class with a default style
	 * 
	 * @param parent its parent window shell
	 */
	public TaskGraphDlg(Shell parent){
		this(parent,SWT.MIN|SWT.CLOSE);
	}
	/**
	 * A constructor class with a proper style
	 * 
	 * @param parent its parent window shell
	 * @param style the window style
	 */
	public TaskGraphDlg(Shell parent, int style) {
		super(parent, style);
		// TODO Auto-generated constructor stub
	}
	/**
	 * Set all of the tasks
	 * 
	 * @param tasks all of the Task object
	 */
	public void setTasks(List<Task> tasks){
		this.tasks = tasks;
	}
	/**
	 * The open function of a dialog class
	 */
	public void open(){
		Shell shell = new Shell(getParent(),getStyle());
		shell.setText("Task Graph");
		createContents(shell);
		shell.open();
		Display display = getParent().getDisplay();
		while(!shell.isDisposed()){
			if(!display.readAndDispatch())
				display.sleep();
		}
	}
	/**
	 * Create all of the contents/controls on this window
	 * 
	 * @param shell its parent window
	 */
	private void createContents(final Shell shell){
		displayTasks(shell);
	}
	/**
	 * Display all of the tasks on the window
	 * 
	 * @param shell its parent window
	 */
	private void displayTasks(final Shell shell){
		int n = tasks.size();

		if( n == 0 ){
			shell.setText("Task Graph(no tasks now...)");
			return;
		}

		LightweightSystem lws = new LightweightSystem(shell);
		Figure contents = new Figure();
		XYLayout contentsLayout = new XYLayout();
		contents.setLayoutManager(contentsLayout);

		int width,height;
		width=height=50;

		int[] orderArray = new int[n];
		int maxX = 0;
		maxX = calculateLayerOrder(orderArray);

		int maxY = 0;
		int[] orderNum = new int[maxX+1];//record the task number in each layer
		for(int i=0;i<=maxX;i++) orderNum[i]=0;
		for(int i =0;i<n;i++){
			orderNum[orderArray[i]]++;
		}
		for(int i=0;i<=maxX;i++){
			if(maxY<orderNum[i])
				maxY=orderNum[i];
		}

		int windowWidth = maxX * width*3/2 + 400;
		int windowHeight = maxY * height*3/2 + 150;
		shell.setSize(windowWidth, windowHeight);

		int terminalX = windowWidth - width - 100;
		int terminalY = windowHeight/2 - 50;

		int[] xArray = new int[n];
		int[] yArray = new int[n];

		for(int i=0;i<=maxX;i++) orderNum[i]=0;//reused to place the task,so re-initialized this array

		for(int i=0;i<n;i++){
			xArray[i] = terminalX - orderArray[i]*width*3/2;
			if(orderNum[orderArray[i]]%2 != 0)
				yArray[i] = terminalY - ((orderNum[orderArray[i]]+1)/2)*height*3/2;
			else
				yArray[i] = terminalY + (orderNum[orderArray[i]]/2)*height*3/2;

			orderNum[orderArray[i]]++;
		}
		TaskFigure[] taskFigure = new TaskFigure[n];
		//draw these tasks
		for(int i=0;i<n;i++){
			taskFigure[i] = new TaskFigure();
			taskFigure[i].setName(tasks.get(i).getName());
			taskFigure[i].setBounds(new Rectangle(xArray[i],yArray[i],width,height));
			contents.add(taskFigure[i]);
			new Dnd(taskFigure[i]);
		}

		//create the connections between tasks
		for(int i=0;i<n;i++){
			Task task = tasks.get(i);
			TaskFigure figure = taskFigure[i];
			if(task.getPreTasks().size()>0){
				Set<Task> pretasks = task.getPreTasks().keySet();
				for(Task pretask:pretasks){
					int preindex = tasks.indexOf(pretask);
					PathFigure path = new PathFigure();
					path.setSourceAnchor(taskFigure[preindex].getOutAnchor());
					path.setTargetAnchor(figure.getInAnchor());
					contents.add(path);
					new Dnd(path);
				}
			}
		}

		lws.setContents(contents);
	}
	/**
	 * Calculate the layer order of a task in the task graph,it will be used
	 * to compute its x-pos in the window;Terminal task will be placed at 
	 * layer zero, and at the right-most place of the window.
	 * 
	 * @param oArray the order array that will be set value
	 * @return the max layer order
	 */
	private int calculateLayerOrder(int[] oArray){
		int n = tasks.size();
		int max = 0;
		ScheduleEngine.getSystem().initialize(tasks);//initialize task graph structure
		for(int i=0;i<n;i++){
			oArray[i] = ScheduleEngine.getSystem().layerOrder(tasks.get(i));
			if(max<oArray[i])
				max = oArray[i];
		}
		return max;
	}
}
/**
 * The task figure class used to show a task
 * 
 * @author zhongnanhai
 *
 */
class TaskFigure extends Figure{
	private String message = new String();
	FixedAnchor inAnchor , outAnchor ;
	public TaskFigure(){
		Color classColor = new Color(null,100,100,206);
		XYLayout layout = new XYLayout();
		setLayoutManager(layout);	
		setBackgroundColor(classColor);
		setOpaque(true);

		inAnchor = new FixedAnchor(this);
		inAnchor.place = new Point(0,1);

		outAnchor = new FixedAnchor(this);
		outAnchor.place = new Point(2,1);
	}
	public void setName(String msg){
		this.message = msg;
		repaint();
	}
	public void paintFigure(Graphics g){
		Rectangle r = bounds;
		g.fillOval(r.x, r.y, r.width-1, r.height-1);
		g.drawText(message, r.x, r.y);
	}
	public FixedAnchor getInAnchor(){
		return inAnchor;
	}
	public FixedAnchor getOutAnchor(){
		return outAnchor;
	}
}
/**
 * A anchor class used to confirm the anchor position 
 * 
 * @author zhongnanhai
 *
 */
class FixedAnchor extends AbstractConnectionAnchor{
	Point place;
	public FixedAnchor(IFigure owner){
		super(owner);
	}
	@Override
	public Point getLocation(Point arg0) {
		// TODO Auto-generated method stub
		Rectangle r = getOwner().getBounds();
		int x = r.x + place.x * r.width/2;
		int y = r.y + place.y * r.height/2;
		Point p = new PrecisionPoint(x,y);
		getOwner().translateToAbsolute(p);
		return p;
	}

}
/**
 * Path figure used to draw connection line
 * 
 * @author zhongnanhai
 *
 */
class PathFigure extends PolylineConnection
{
	public PathFigure()
	{
		PolygonDecoration decoration = new PolygonDecoration();
		decoration.setLineWidth(3);
		decoration.setForegroundColor(new Color(null,255,0,0));
		setTargetDecoration(decoration);
		setConnectionRouter(ConnectionRouter.NULL);
	}
}
/**
 * This class is used to implement drag-and-drop function
 * 
 * @author zhongnanhai
 *
 */
class Dnd extends MouseMotionListener.Stub
implements MouseListener
{
	public Dnd(IFigure figure)
	{
		figure.addMouseMotionListener(this);
		figure.addMouseListener(this);
	}
	Point start;
	public void mouseReleased(MouseEvent e){}
	public void mouseClicked(MouseEvent e){}
	public void mouseDoubleClicked(MouseEvent e){}
	public void mousePressed(MouseEvent e)
	{
		start = e.getLocation();
		e.consume();//add
	}
	public void mouseDragged(MouseEvent e)
	{
		if(start==null)
			return;
		Point p = e.getLocation();
		Dimension d = p.getDifference(start);
		start = p;
		Figure f = ((Figure)e.getSource());
		Rectangle r = f.getBounds().getTranslated(d.width, d.height);
		f.setBounds(r);
		f.getParent().getLayoutManager().setConstraint(f, r);
	}
}
